package edu.ensmp.ckEditor.mousePlugin;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
import javax.swing.JComponent;


import org.apache.commons.collections15.Factory;


import edu.ensmp.ckEditor.ckModel.CKModel.Concept;
import edu.ensmp.ckEditor.ckModel.CKModel.Edge;
import edu.ensmp.ckEditor.ckModel.CKModel.Knowledge;
import edu.ensmp.ckEditor.main.Launcher;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.util.ArrowFactory;

/**
 * A plugin that can create vertices, undirected edges, and directed edges using
 * mouse gestures.
 * 
 * @author Tom Nelson
 * 
 */



public class EditingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
		implements MouseListener, MouseMotionListener{

	protected V startVertex;
	protected Point2D down;
	protected int cptVecteur = 0;

	protected CubicCurve2D rawEdge = new CubicCurve2D.Float();
	protected Shape edgeShape;
	protected Shape rawArrowShape;
	protected Shape arrowShape;
	protected VisualizationServer.Paintable edgePaintable;
	protected VisualizationServer.Paintable arrowPaintable;
	protected EdgeType edgeIsDirected;
	protected Factory<V> vertexFactory;
	protected Factory<E> edgeFactory;
	
	public static boolean save = true;

	public EditingGraphMousePlugin(Factory<V> vertexFactory, Factory<E> edgeFactory) {
		this(MouseEvent.BUTTON1_MASK, vertexFactory, edgeFactory);
	}

	/**
	 * create instance and prepare shapes for visual effects
	 * 
	 * @param modifiers
	 */
	public EditingGraphMousePlugin(int modifiers, Factory<V> vertexFactory,
			Factory<E> edgeFactory) {
		super(modifiers);
		this.vertexFactory = vertexFactory;
		this.edgeFactory = edgeFactory;
		rawEdge.setCurve(0.0f, 0.0f, 0.33f, 100, .66f, -50, 1.0f, 0.0f);
		rawArrowShape = ArrowFactory.getNotchedArrow(20, 16, 8);
		edgePaintable = new EdgePaintable();
		arrowPaintable = new ArrowPaintable();
		this.cursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
	}

	/**
	 * Overridden to be more flexible, and pass events with key combinations.
	 * The default responds to both ButtonOne and ButtonOne+Shift
	 */
	@Override
	public boolean checkModifiers(MouseEvent e) {
		return (e.getModifiers() & modifiers) != 0;
	}

	/**
	 * If the mouse is pressed in an empty area, create a new vertex there. If
	 * the mouse is pressed on an existing vertex, prepare to create an edge
	 * from that vertex to another
	 */
	@SuppressWarnings("unchecked")
	public void mousePressed(MouseEvent e) {
			if (checkModifiers(e)) {
				
				final VisualizationViewer<V, E> vv = (VisualizationViewer<V, E>) e
						.getSource();
				final Point2D p = e.getPoint();
				GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
				if (pickSupport != null) {
					Graph<V, E> graph = vv.getModel().getGraphLayout().getGraph();
					// set default edge type
					if (graph instanceof DirectedGraph) {
						edgeIsDirected = EdgeType.DIRECTED;
					} else {
						edgeIsDirected = EdgeType.UNDIRECTED;
					}
	
					final V vertex = pickSupport.getVertex(vv.getModel()
							.getGraphLayout(), p.getX(), p.getY());
					
					if (vertex != null) { // get ready to make an edge
						
						startVertex = vertex;
						down = e.getPoint();
						transformEdgeShape(down, down);
						vv.addPostRenderPaintable(edgePaintable);
						if ((e.getModifiers() & MouseEvent.SHIFT_MASK) != 0
								&& vv.getModel().getGraphLayout().getGraph() instanceof UndirectedGraph == false) {
							edgeIsDirected = EdgeType.DIRECTED;
						}
						if (edgeIsDirected == EdgeType.DIRECTED) {
							transformArrowShape(down, e.getPoint());
							vv.addPostRenderPaintable(arrowPaintable);
						}
					} else if (!Launcher.getEditorGUI().getPropositionTextField().getText().isEmpty() && e.getClickCount() == 2){ // make a new vertex

						V newVertex = vertexFactory.create();
						Layout<V, E> layout = vv.getModel().getGraphLayout();
						graph.addVertex(newVertex);
						layout.setLocation(newVertex, vv.getRenderContext()
								.getMultiLayerTransformer().inverseTransform(
										e.getPoint()));
						
						/**
						 * Mise en mémoire dans des vecteurs différents 
						 * des vertex créés dans les deux panels 
						 * pour pouvoir réutiliser ceux ci au moment 
						 * de la sauvergarde de l'arbre C-K
						 */
						if(vv.getName() == "concept"){
							 ((Concept) newVertex).setPosition(e.getPoint());
							 ((Concept) newVertex).setVecteurConceptSave();
							 save = false;
						} else {
							((Knowledge) newVertex).setPosition(e.getPoint());
							((Knowledge) newVertex).setVecteurKnowledgeSave();
							save = false;
						}
					}
				}
				vv.repaint();
			}
	}
	

	/**
	 * If startVertex is non-null, and the mouse is released over an existing
	 * vertex, create an undirected edge from startVertex to the vertex under
	 * the mouse pointer. If shift was also pressed, create a directed edge
	 * instead.
	 */
	@SuppressWarnings("unchecked")
	public void mouseReleased(MouseEvent e) throws NullPointerException {
		if (checkModifiers(e)) {
			final VisualizationViewer<V, E> vv = (VisualizationViewer<V, E>) e
					.getSource();
			final Point2D p = e.getPoint();
			Layout<V, E> layout = vv.getModel().getGraphLayout();
			GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
			if (pickSupport != null) {
				final V vertex = pickSupport.getVertex(layout, p.getX(), p
						.getY());
				if (vertex != null && startVertex != null) {
					Integer cptEdge = (Integer) edgeFactory.create();
					if (vv.getName() == "concept"){
						Edge newEdge = new Edge(cptEdge);
						newEdge.setIndiceDebut(((Concept) startVertex).getIndex());
						newEdge.setIndiceFin(((Concept) vertex).getIndex());
						newEdge.setVecEdgeSave();
					}
					
					Graph<V, E> graph = vv.getGraphLayout().getGraph();
					graph.addEdge((E) cptEdge, startVertex, vertex,
							edgeIsDirected);
					Launcher.getLogger().addLine(
							"New Edge: [" + startVertex.toString() + "] --> ["
									+ vertex.toString() + "]");
					vv.repaint();
					EditingGraphMousePlugin.save = false;
				}
			}
			startVertex = null;
			down = null;
			edgeIsDirected = EdgeType.UNDIRECTED;
			vv.removePostRenderPaintable(edgePaintable);
			vv.removePostRenderPaintable(arrowPaintable);
		}
		
	}

	/**
	 * If startVertex is non-null, stretch an edge shape between startVertex and
	 * the mouse pointer to simulate edge creation
	 */
	@SuppressWarnings("unchecked")
	public void mouseDragged(MouseEvent e) {
		if (checkModifiers(e)) {
			if (startVertex != null) {
				transformEdgeShape(down, e.getPoint());
				if (edgeIsDirected == EdgeType.DIRECTED) {
					transformArrowShape(down, e.getPoint());
				}
			}
			VisualizationViewer<V, E> vv = (VisualizationViewer<V, E>) e
					.getSource();
			vv.repaint();
		}
	}

	/**
	 * code lifted from PluggableRenderer to move an edge shape into an
	 * arbitrary position
	 */
	private void transformEdgeShape(Point2D down, Point2D out) {
		float x1 = (float) down.getX();
		float y1 = (float) down.getY();
		float x2 = (float) out.getX();
		float y2 = (float) out.getY();

		AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);

		float dx = x2 - x1;
		float dy = y2 - y1;
		float thetaRadians = (float) Math.atan2(dy, dx);
		xform.rotate(thetaRadians);
		float dist = (float) Math.sqrt(dx * dx + dy * dy);
		xform.scale(dist / rawEdge.getBounds().getWidth(), 1.0);
		edgeShape = xform.createTransformedShape(rawEdge);
	}

	private void transformArrowShape(Point2D down, Point2D out) {
		float x1 = (float) down.getX();
		float y1 = (float) down.getY();
		float x2 = (float) out.getX();
		float y2 = (float) out.getY();

		AffineTransform xform = AffineTransform.getTranslateInstance(x2, y2);

		float dx = x2 - x1;
		float dy = y2 - y1;
		float thetaRadians = (float) Math.atan2(dy, dx);
		xform.rotate(thetaRadians);
		arrowShape = xform.createTransformedShape(rawArrowShape);
	}

	/**
	 * Used for the edge creation visual effect during mouse drag
	 */
	class EdgePaintable implements VisualizationServer.Paintable {

		public void paint(Graphics g) {
			if (edgeShape != null) {
				Color oldColor = g.getColor();
				g.setColor(Color.black);
				((Graphics2D) g).draw(edgeShape);
				g.setColor(oldColor);
			}
		}

		public boolean useTransform() {
			return false;
		}
	}

	/**
	 * Used for the directed edge creation visual effect during mouse drag
	 */
	class ArrowPaintable implements VisualizationServer.Paintable {

		public void paint(Graphics g) {
			if (arrowShape != null) {
				Color oldColor = g.getColor();
				g.setColor(Color.black);
				((Graphics2D) g).fill(arrowShape);
				g.setColor(oldColor);
			}
		}

		public boolean useTransform() {
			return false;
		}
	}

	public void mouseEntered(MouseEvent e) {
		JComponent c = (JComponent) e.getSource();
		c.setCursor(cursor);
	}

	public void mouseExited(MouseEvent e) {
		JComponent c = (JComponent) e.getSource();
		c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void mouseClicked(MouseEvent e) {		
	}	
	
}